﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;

    using Phuse.IO;

    /// <summary>Represents a stream, through which the bytes of a single stage command can be written.</summary>
    /// <remarks>Commands that have multiple stages must use a separate object of this class for each stage.</remarks>
    /// <threadsafety static="true" instance="false"/>
    public sealed class CommandStream : BufferStream
    {
        private readonly bool isMultiline;
        private bool lastWasCarriageReturnOrLineFeed;
        private static readonly byte[] TerminatingLine = new byte[] { (byte)'.', (byte)'\r', (byte)'\n' };

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="Stream.Read"/>.</summary>
        /// <exception cref="NotSupportedException">Thrown with each call.</exception>
        public sealed override int Read(byte[] buffer, int offset, int count)
        {
            throw CreateNotSupportedException();
        }

        /// <summary>See <see cref="Stream.Write"/>.</summary>
        /// <remarks>For multi-line commands, the bytes written through this method are automatically dot-stuffed, see
        /// <a href="http://www.ietf.org/rfc/rfc3977.txt">RFC 3977</a> 3.1.1.</remarks>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Validated with call to BufferHelper.AssertValidRange.")]
        public sealed override void Write(byte[] buffer, int offset, int count)
        {
            this.AssertNotDisposed();
            BufferHelper.AssertValidRange(buffer, "buffer", offset, "offset", count, "count");

            unchecked
            {
                WriteBuffer writeBuffer = this.WriteBuffer;
                int pastEnd = offset + count;
                byte currentByte;

                while ((offset < pastEnd) &&
                    ((writeBuffer.Count < writeBuffer.Capacity) || writeBuffer.Flush()))
                {
                    currentByte = buffer[offset];

                    // This function may be called by client code with data that does not use proper "\r\n" sequences
                    // for line breaks. Since non-standard line breaks are converted to standard ones before data is
                    // sent to the server, we only need to establish whether the last character was '\r' *or* '\n'.
                    if (this.lastWasCarriageReturnOrLineFeed)
                    {
                        switch (currentByte)
                        {
                            case (byte)'\r':
                            case (byte)'\n':
                                ++offset;
                                break;
                            case (byte)'.':
                                // We're not incrementing offset here to achieve dot-stuffing
                                this.lastWasCarriageReturnOrLineFeed = false;
                                break;
                            default:
                                ++offset;
                                this.lastWasCarriageReturnOrLineFeed = false;
                                break;
                        }
                    }
                    else
                    {
                        ++offset;
                        this.lastWasCarriageReturnOrLineFeed =
                            (currentByte == (byte)'\r') || (currentByte == (byte)'\n');
                    }

                    writeBuffer[writeBuffer.Count++] = currentByte;
                }
            }
        }

        /// <summary>See <see cref="Stream.Flush"/>.</summary>
        public sealed override void Flush()
        {
            // It would partly defeat the advantages of buffering if we flushed our writeBuffer field here. It is more
            // efficient to wait until just before a response is requested.
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal CommandStream(WriteBuffer writeBuffer, bool isMultiline) : base(null, writeBuffer)
        {
            this.isMultiline = isMultiline;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="Stream.Dispose(bool)"/>.</summary>
        /// <remarks>If the last two bytes written were not a "\r\n" sequence then writes such a sequence. Moreover, if
        /// the command was created as multi-line then also writes the terminating ".\r\n" sequence as required by
        /// <a href="http://www.ietf.org/rfc/rfc3977.txt">RFC 3977</a> 3.1.1.</remarks>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Dispose() must never throw.")]
        protected sealed override void Dispose(bool disposing)
        {
            try
            {
                if (disposing && !this.IsDisposed)
                {
                    WriteBuffer writeBuffer = this.WriteBuffer;

                    if (!this.lastWasCarriageReturnOrLineFeed)
                    {
                        WriteRaw(writeBuffer, StreamHelper.LineBreak);
                    }

                    if (this.isMultiline)
                    {
                        WriteRaw(writeBuffer, TerminatingLine);
                    }
                }
            }
            catch
            {
            }
            finally
            {
                base.Dispose(disposing);
            }
        }

        private static void WriteRaw(WriteBuffer writeBuffer, byte[] buffer)
        {
            // Unfortunately, some of what follows is duplicated from the Write method. This could only be
            // avoided at the cost of sacrificing quite a bit of execution speed in the emulator (and thus
            // on devices).
            int offset = 0;

            while ((offset < buffer.Length) && ((writeBuffer.Count < writeBuffer.Capacity) || writeBuffer.Flush()))
            {
                writeBuffer[writeBuffer.Count++] = buffer[offset++];
            }
        }
    }
}
